﻿using System;
using System.Text;

using System.Threading;

using System.Net;
using System.Net.Sockets;

namespace UdpAsyncEchoServer
{
    class UdpServer
    {
        private Socket m_udpsock;
        private int m_port;
        // sending and receiving has its own memory buffer to avoid race condition
        private byte[] m_recvBuffer = new byte[1024];

        /// <remark>
        /// seperating buffer for receving and sending, that is to say, an isolated buffer for receiving, and an individual buffer for sending, can only prevent the race condition between receiving thread and sending thread, which can not avoid race condition among multiple sending threads
        /// in this example, there is no race condition among receiving threads, because that a new asynchronous reading will only be invoked in the callback of the previous asynchronous reading, that is to say, receiving operation is sequenced.
        /// but if we post an asynchronous sending in each receiving callback, multiple asynchronous sending will take place simultaneously. if all these simultaneous sending threads share the same buffer containg the content, then serious race condition occurs
        /// seperating buffers for receiving and sending can only eliminate the race condion in the same "receiving-echo" session. but using only one buffer to hold the content to be sent out, still exists the race condition among receiving threads and sending threads in different "receiving-echo" session
        /// </remark>
        // private byte[] m_sendBuffer = new byte[1024];

        public UdpServer(int port)
        {
            m_udpsock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            m_port = port;
        }

        public void Start()
        {
            m_udpsock.Bind(new IPEndPoint(IPAddress.Any, m_port));
            Read();
            PrintCurrentThreadName("Start");
        }

        public void Stop()
        {
            m_udpsock.Close();
        }

        private void Read()
        {
            EndPoint clientEndpoint = new IPEndPoint(IPAddress.Any, 0);
            m_udpsock.BeginReceiveFrom(m_recvBuffer, 0, m_recvBuffer.Length, SocketFlags.None, ref clientEndpoint, OnReceiveCompleted, null);
        }

        /// <remark>
        /// [FROM MSDN]
        /// When your application calls BeginReceiveFrom, the system will use a separate thread to 
        /// execute the specified callback method, and it will block on EndReceiveFrom until the Socket 
        /// reads data or throws an exception
        /// !!!!!!!!!!!!! above words explains the mechanism of the asynchronous IO .net provided
        /// the whole mechanism is just based on multi-thread, or just a wrapper of multi-thread
        /// when BeginXXX is invoked, the CLR will use another thread to execute the callback,(this working thread
        /// must be from thread-pool to save system resource)
        /// but the callback will block that thread, which will not consume much CPU to achieve high efficiency
        /// </remark>
        private void OnReceiveCompleted(IAsyncResult asyncResult)
        {
            EndPoint clientEndpoint = new IPEndPoint(IPAddress.Any, 0);
            int recvLength = m_udpsock.EndReceiveFrom(asyncResult, ref clientEndpoint);

            string recvMsg = Encoding.ASCII.GetString(m_recvBuffer, 0, recvLength);
            Console.WriteLine("[ASYNC SERVER] received [{0}] from client[{1}]", recvMsg, clientEndpoint.ToString());

            //================== SERIOUS PROBLEM ======================//
            // below codes have serious problem which will result in race condition
            // below codes lauch "Asynchronous Read" and "Asynchronous Send" simultaneously
            // but this two asynchronous IO operation share the same buffer, which causes race condition
            // there are two solution:
            // 1.   Do not lauch "asynchronous read" and "asynchronous send" simultaneously,
            //      but serialize both IO operation
            //      we can abandon "BeginSendTo", but use "SendTo" instead
            //      or we will not call "Read" right after "BeginSendTo", but call it in the "sent callback"
            //      of "BeginSendTo"
            // 2.   Do not share the same buffer. Copy the buffer for receiving to a buffer to sending
            //      each time. Since this application is special(receiving buffer has fixed length), we can
            //      create a specific buffer for sending, and need not to create a new sending buffer each time
            //
            // m_udpsock.BeginSendTo(m_recvBuffer, 0, recvLength, SocketFlags.None, clientEndpoint, OnSendCompleted, null);
            // Read();
            //================== SERIOUS PROBLEM ======================//

            // can not launch asynchronous sending in the callback of asynchronous receiving, 
            // which will result in simultaneous writing to the socket's sending buffer, 
            // leading to serious race condition to socket's sending buffer
            // post asynchronous sending with sending buffer
            // !!!!!!!!!!!!!!!!!
            // since asynchronous sending is forbidden, only one thread access the buffer at one
            // instance, so no need to copy the buffer
            m_udpsock.SendTo(m_recvBuffer,0,recvLength,SocketFlags.None,clientEndpoint);

            // post asynchronous receiving with receiving buffer. No share, No race
            Read();
            PrintCurrentThreadName("OnReceiveCompleted");
        }

        /*
         * can not launch asynchronous sending in the callback of asynchronous receiving, which
         * will result in simultaneous writing to the socket's sending buffer, leading to serious
         * race condition to socket's sending buffer
        private void OnSendCompleted(IAsyncResult asyncResult)
        {
            m_udpsock.EndSendTo(asyncResult);
            PrintCurrentThreadName("OnSendCompleted");
        }
         * */

        /// <summary>
        /// has no effect, for the thread name is null
        /// </summary>
        private void PrintCurrentThreadName(string prefix)
        {
            // Console.WriteLine("************* <{0}>'s thread = {1}",prefix,Thread.CurrentThread.Name);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            UdpServer svr = new UdpServer(6001);
            svr.Start();

            Console.ReadLine();
            svr.Stop();
            Console.WriteLine("server is closed, program exits");
        }
    }
}
